﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using PaperRenderingInterfaces;
using DeepZoomPivotConstructor.PivotLib;
using HtmlAgilityPack;
using PaperPluginUtilities;

namespace HTMLPageScanners
{
    /// <summary>
    /// Scan the given HTML page and the pages that are at the same level below it, and linked to it. Each of those
    /// pages is re-scanned. So there could be duplicates that show up - but the Render Engine's duplicate remover should
    /// kill that off.
    /// 
    /// Call with an HTML guy in the header, and with the following prefixes to that HTML:
    /// 
    /// /PrefixInfo/http://blah.blah.blah
    /// 
    /// Where PrefixInfo is a comma seperated list name=value pairs:
    /// 
    /// UseTopPage=[true,false] - The top page might contain plots, so use it
    /// PlotPageScanner=[name] - The PageRender name to call to do sub-page scanning.
    /// 
    /// </summary>
    [Export(typeof(IPaperRender))]
    [PaperRender("MasterHTMLRenderHTMLAndFurther")]
    public class ScanThisPageAndBelow : IPaperRender
    {
        /// <summary>
        /// Access the web as we need.
        /// </summary>
        [Import(typeof(IWebAccess))]
        IWebAccess WebGetter;

        /// <summary>
        /// So we can do the rendering of the notes we find.
        /// </summary>
        [Import]
        IPaperRenderEngine MyPaperRenderEngine;

        public IEnumerable<PivotImage> Render(string renderAddress)
        {
            ///
            /// Parse the address
            /// 

            var prms = ParseConfigString(renderAddress);

            ///
            /// First, get the html back
            /// 

            Uri sourcePage = new Uri(prms["url"]);
            string html = WebGetter.GetUriAsString(sourcePage);
            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(html);

            Uri baseUri = GetBaseUri(sourcePage);

            string title = doc.DocumentNode.ExtractHTMLTitle();

            ///
            /// Now, figure out what we will use to scan the subpages.
            /// 

            Func<string, IEnumerable<PivotImage>> renderSubPage = uri => MyPaperRenderEngine.Render(prms["PlotPageScanner"], uri);
            Func<string, IEnumerable<PivotImage>> renderMainPage = uri => MyPaperRenderEngine.Render(prms["PlotPageScanner"], uri);
            if (prms.ContainsKey("Recursive") && prms["Recursive"] == "true")
            {
                prms["UseTopPage"] = "true";
                renderSubPage = uri => MyPaperRenderEngine.Render("MasterHTMLRenderHTMLAndFurther", BuildRenderParamAddr(prms, uri));
            }

            ///
            /// if the URL is of the form http://blah.com/dude/junk.html then we want to
            /// find everything under that dude. First, find all the links we can in the
            /// html we were given.
            /// 

            var nodes = new EnumerableWrapper<HtmlNode>(doc.DocumentNode.SelectNodes("//a[@href]"));
            var goodLinks = from n in nodes
                            let url = n.Attributes["href"]
                            where url != null
                            let uri = new Uri(sourcePage, url.Value)
                            where uri.OriginalString.StartsWith(baseUri.OriginalString) && IsHTMLPage(uri.OriginalString) && uri.OriginalString != sourcePage.OriginalString
                            from img in renderSubPage(uri.OriginalString).Apply(i => AddImageTitle(i, title))
                            select img;

            if (ParamsHasValue(prms, "UseTopPage", "false") != "false")
            {
                var hostLinks = renderMainPage(sourcePage.OriginalString).Apply(i => AddImageTitle(i, title));
                goodLinks = hostLinks.Concat(goodLinks);
            }

            ///
            /// Last, if the URL is not this guy, then we have to move it down - this is so
            /// the top level page that does his is the "Main Note Page" and the page onwhich
            /// the plots occur is the Plot Page.
            /// 

            goodLinks = goodLinks.Apply(pi => FixupMainPageLink(pi, sourcePage));

            return goodLinks;
        }

        /// <summary>
        /// Fix up a PivotImage links to make sure Main Note Page is this sourcePage.
        /// </summary>
        /// <param name="sourcePage"></param>
        /// <returns></returns>
        private void FixupMainPageLink(PivotImage pi, Uri sourcePage)
        {
            ///
            /// First, find an old Main Note Page item. If it is there, then clear it out.
            /// 

            var oldMainMetaData = pi.GetMetadata<PivotMetadataURL>("More Information").Where(pm => pm.LinkText == "Main Note Page").FirstOrDefault();
            if (oldMainMetaData != null)
            {
                pi.Remove<PivotMetadataURL>("More Information", m => m.LinkText == "Main Note Page");
            }

            ///
            /// Reset it to be the correct one
            /// 

            pi.Add("More Information", "Main Note Page", sourcePage);

            ///
            /// If it isn't there already, create a Plot Page entry. If the two are going to be the same, then skip out the plot page
            /// 

            if (oldMainMetaData != null && sourcePage.OriginalString != oldMainMetaData.LinkUri.OriginalString)
            {
                if (pi.GetMetadata<PivotMetadataURL>("More Information").Where(m => m.LinkText == "Plot Page").Count() == 0)
                {
                    pi.Add("More Information", "Plot Page", oldMainMetaData.LinkUri);
                }
            }
        }

        /// <summary>
        /// Returns the string to re-invoke this guy, but with a new http address (passed in as an argument).
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        private string BuildRenderParamAddr(Dictionary<string, string> prms, string uri)
        {
            StringBuilder r = new StringBuilder();
            r.Append("/");
            bool isFirst = true;
            foreach (var item in prms.Keys.Where(s => s != "url").OrderBy(s => s))
            {
                if (!isFirst)
                {
                    r.Append(",");
                }
                isFirst = false;
                r.AppendFormat("{0}={1}", item, prms[item]);
            }
            r.AppendFormat("/{0}", uri);
            return r.ToString();
        }

        /// <summary>
        /// Helper function to give a default value to a parameter.
        /// </summary>
        /// <param name="prms"></param>
        /// <param name="paramName"></param>
        /// <param name="defaultValue"></param>
        /// <returns></returns>
        private string ParamsHasValue(Dictionary<string, string> prms, string paramName, string defaultValue)
        {
            if (!prms.ContainsKey(paramName))
            {
                return defaultValue;
            }
            return prms[paramName];
        }

        /// <summary>
        /// We parse the string for the information required.
        /// The format is /xxx/http:// where xxx is a comma seperated list of name=value pairs.
        /// </summary>
        /// <param name="renderAddress"></param>
        /// <returns></returns>
        private Dictionary<string, string> ParseConfigString(string renderAddress)
        {
            if (renderAddress == null || renderAddress.Length == 0)
            {
                throw new ArgumentException("Can not be null or empty string!", "renderAddress");
            }

            ///
            /// Create the basics of what we are going to be returning for parameters, and
            /// also fill in default settings.
            /// 

            Dictionary<string, string> result = new Dictionary<string, string>();
            result["PlotPageScanner"] = "HTMLSimpleTableCells";

            ///
            /// Split off the URL and the settings stuff. If there are no settings we take
            /// defaults above.
            /// 

            if (renderAddress[0] != '/')
            {
                result["url"] = renderAddress;
                return result;
            }

            string parameters = renderAddress.Substring(1);
            int closeingSlash = parameters.IndexOf("/");
            result["url"] = parameters.Substring(closeingSlash+1);
            parameters = parameters.Substring(0, closeingSlash);

            var nameValue = from p in parameters.Split(',')
                            let nv = p.Split(new char[] {'='}, 2)
                            select nv;
            foreach (var item in nameValue)
            {
                if (item.Length == 2)
                {
                    result[item[0]] = item[1];
                } if (item.Length == 1)
                {
                    result["url"] = item[0];
                }
            }
            return result;
        }

        /// <summary>
        /// Add a title if it isn't null
        /// </summary>
        /// <param name="i"></param>
        /// <param name="title"></param>
        /// <returns></returns>
        private void AddImageTitle(PivotImage i, string title)
        {
            if (title != null && title.Length != 0)
            {
                i.Remove("Note Title");
                i.Add("Note Title", title);
            }
        }

        /// <summary>
        /// Get the title of the note - which is just the title of the web page.
        /// </summary>
        /// <param name="uri"></param>
        /// <returns></returns>
        private string GetTitle(Uri uri)
        {
            string html = WebGetter.GetUriAsString(uri);
            HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(html);
            return doc.DocumentNode.ExtractHTMLTitle();
        }

        /// <summary>
        /// True if this is likely going to point to a html pgae
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        private bool IsHTMLPage(string p)
        {
            if (p.EndsWith(".html") || p.EndsWith(".htm") || p.EndsWith("/"))
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// Given a URI, get its base directory (or if it is a directory...).
        /// </summary>
        /// <param name="sourcePage"></param>
        /// <returns></returns>
        private Uri GetBaseUri(Uri sourcePage)
        {
            if (!IsHTMLPage(sourcePage.OriginalString))
            {
                return sourcePage;
            }

            string finalUri = sourcePage.OriginalString;
            finalUri = finalUri.Substring(0, finalUri.Length - sourcePage.Segments[sourcePage.Segments.Length-1].Length);
            return new Uri(finalUri);
        }

    }
}
